﻿' Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

Imports System.Collections.Immutable
Imports System.Runtime.InteropServices
Imports System.Threading
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports TypeKind = Microsoft.CodeAnalysis.TypeKind

Namespace Microsoft.CodeAnalysis.VisualBasic

    Partial Class Binder

        Private Function BindConditionalAccessExpression(node As ConditionalAccessExpressionSyntax, diagnostics As DiagnosticBag) As BoundExpression
            Dim placeholder As BoundValuePlaceholderBase = Nothing
            Dim boundExpression As BoundExpression = BindConditionalAccessReceiver(node, diagnostics, placeholder)

            Dim accessBinder As New ConditionalAccessBinder(Me, node, placeholder)

            Dim whenNotNull As BoundExpression = accessBinder.BindExpression(node.WhenNotNull, diagnostics)

            ' Do not know result type yet, it will be determined based on usage.
            Return New BoundConditionalAccess(node, boundExpression, placeholder, whenNotNull, Nothing)
        End Function

        Private Function BindConditionalAccessReceiver(node As ConditionalAccessExpressionSyntax, diagnostics As DiagnosticBag, <Out> ByRef placeholder As BoundValuePlaceholderBase) As BoundExpression
            Dim boundExpression As BoundExpression

            If node.Expression Is Nothing Then
                boundExpression = TryBindOmittedLeftForConditionalAccess(node, Me, diagnostics)

                If boundExpression Is Nothing Then
                    ' Didn't find binder that can handle conditional access with omitted left part
                    boundExpression = ReportDiagnosticAndProduceBadExpression(diagnostics, node, ERRID.ERR_BadConditionalWithRef).MakeCompilerGenerated()
                End If
            Else
                ' Bind the expression as a value
                boundExpression = BindValue(node.Expression, diagnostics)

                ' NOTE: If the expression is not an l-value we should make an r-value of it
                If Not boundExpression.IsLValue Then
                    boundExpression = Me.MakeRValue(boundExpression, diagnostics)
                End If
            End If

            Dim type As TypeSymbol = boundExpression.Type

            Dim placeholderType As TypeSymbol = type
            Dim placeholderIsLValue = boundExpression.IsLValue OrElse boundExpression.IsMeReference

            If type.IsValueType Then
                If type.IsNullableType() Then
                    placeholderType = type.GetNullableUnderlyingType()
                    placeholderIsLValue = False

                    If boundExpression.IsLValue Then
                        boundExpression = Me.MakeRValue(boundExpression, diagnostics)
                    End If
                Else
                    ' Operator '{0}' is not defined for type '{1}'.
                    ReportDiagnostic(diagnostics, node.QuestionMarkToken, ERRID.ERR_UnaryOperand2, node.QuestionMarkToken.ValueText, type)
                End If
            End If

            ' Create a placeholder
            If placeholderIsLValue Then
                placeholder = New BoundLValuePlaceholder(node, placeholderType)
            Else
                placeholder = New BoundRValuePlaceholder(node, placeholderType)
            End If

            placeholder.SetWasCompilerGenerated()

            Return boundExpression
        End Function

        Protected Overridable Function TryBindOmittedLeftForConditionalAccess(node As ConditionalAccessExpressionSyntax,
                                                                             accessingBinder As Binder,
                                                                             diagnostics As DiagnosticBag) As BoundExpression
            Debug.Assert(Me.ContainingBinder IsNot Nothing)
            Return Me.ContainingBinder.TryBindOmittedLeftForConditionalAccess(node, accessingBinder, diagnostics)
        End Function

        Protected Function GetConditionalAccessReceiver(node As ConditionalAccessExpressionSyntax) As BoundExpression
            Dim result As BoundExpression = TryGetConditionalAccessReceiver(node)

            If result Is Nothing Then
                Dim placeholder As BoundValuePlaceholderBase = Nothing
                BindConditionalAccessReceiver(node, New DiagnosticBag(), placeholder)
                Return placeholder
            End If

            Return result
        End Function

        Protected Overridable Function TryGetConditionalAccessReceiver(node As ConditionalAccessExpressionSyntax) As BoundExpression
            Return Me.ContainingBinder.TryGetConditionalAccessReceiver(node)
        End Function
    End Class

    ''' <summary>
    ''' A helper to bind conditional access. 
    ''' </summary>
    Friend NotInheritable Class ConditionalAccessBinder
        Inherits Binder

        Private ReadOnly m_ConditionalAccess As ConditionalAccessExpressionSyntax
        Private ReadOnly m_Placeholder As BoundValuePlaceholderBase

        Public Sub New(containingBinder As Binder, conditionalAccess As ConditionalAccessExpressionSyntax, placeholder As BoundValuePlaceholderBase)
            MyBase.New(containingBinder)
            m_ConditionalAccess = conditionalAccess
            m_Placeholder = placeholder
        End Sub

        Protected Overrides Function TryGetConditionalAccessReceiver(node As ConditionalAccessExpressionSyntax) As BoundExpression
            If node Is m_ConditionalAccess Then
                Return m_Placeholder
            End If

            Return MyBase.TryGetConditionalAccessReceiver(node)
        End Function
    End Class

End Namespace
